package org.mctourney.autoreferee.commands;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.cli.CommandLine;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import org.mctourney.autoreferee.AutoRefMatch;
import org.mctourney.autoreferee.AutoRefTeam;
import org.mctourney.autoreferee.AutoReferee;
import org.mctourney.autoreferee.event.player.PlayerTeamJoinEvent;
import org.mctourney.autoreferee.util.commands.AutoRefCommand;
import org.mctourney.autoreferee.util.commands.AutoRefPermission;
import org.mctourney.autoreferee.util.commands.CommandHandler;

public class ScoreboardCommands implements CommandHandler
{
	private static final List<String> OBJECTIVES_CRITERIA = ImmutableList.of("health", "playerKillCount", "totalKillCount", "deathCount", "dummy");
	private static final Map<String, DisplaySlot> OBJECTIVES_DISPLAYSLOT = ImmutableMap.of("belowName", DisplaySlot.BELOW_NAME, "list", DisplaySlot.PLAYER_LIST, "sidebar", DisplaySlot.SIDEBAR);
	private static final Map<String, ChatColor> TEAMS_OPTION_COLOR = ImmutableMap.<String, ChatColor>builder()
			.put("aqua", ChatColor.AQUA)
			.put("black", ChatColor.BLACK)
			.put("blue", ChatColor.BLUE)
			.put("bold", ChatColor.BOLD)
			.put("dark_aqua", ChatColor.DARK_AQUA)
			.put("dark_blue", ChatColor.DARK_BLUE)
			.put("dark_gray", ChatColor.DARK_GRAY)
			.put("dark_green", ChatColor.DARK_GREEN)
			.put("dark_purple", ChatColor.DARK_PURPLE)
			.put("dark_red", ChatColor.DARK_RED)
			.put("gold", ChatColor.GOLD)
			.put("gray", ChatColor.GRAY)
			.put("green", ChatColor.GREEN)
			.put("italic", ChatColor.ITALIC)
			.put("light_purple", ChatColor.LIGHT_PURPLE)
			.put("obfuscated", ChatColor.MAGIC) // This is the important line
			.put("red", ChatColor.RED)
			.put("reset", ChatColor.RESET)
			.put("strikethrough", ChatColor.STRIKETHROUGH)
			.put("underline", ChatColor.UNDERLINE)
			.put("white", ChatColor.WHITE)
			.put("yellow", ChatColor.YELLOW)
			.build();

	AutoReferee plugin;

	public ScoreboardCommands(Plugin plugin)
	{
		this.plugin = (AutoReferee) plugin;
	}

	@AutoRefCommand(name={"scoreboard"}, argmin=1,
		description="Replacement for Bukkit's /scoreboard to fix multiworld support.")
	@AutoRefPermission(console=true, nodes={"bukkit.command.scoreboard"})

	public boolean scoreboard(CommandSender sender, AutoRefMatch match, String[] args, CommandLine options)
	{
		World world = plugin.getSenderWorld(sender);
		final Scoreboard scoreboard = AutoReferee.getInstance().getWorldScoreboard(world);

		// the remainder of this method is the property of the Bukkit dev team, to be updated
		// in the event that this command's format or behavior changes appreciably

		if (args[0].equalsIgnoreCase("objectives")) {
			if (args.length == 1) {
				sender.sendMessage(ChatColor.RED + "Usage: /scoreboard objectives <list|add|remove|setdisplay>");
				return false;
			}
			if (args[1].equalsIgnoreCase("list")) {
				Set<Objective> objectives = scoreboard.getObjectives();
				if (objectives.isEmpty()) {
					sender.sendMessage(ChatColor.RED + "There are no objectives on the scoreboard");
					return false;
				}
				sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + objectives.size() + " objective(s) on scoreboard");
				for (Objective objective : objectives) {
					sender.sendMessage("- " + objective.getName() + ": displays as '" + objective.getDisplayName() + "' and is type '" + objective.getCriteria() + "'");
				}
			} else if (args[1].equalsIgnoreCase("add")) {
				if (args.length < 4) {
					sender.sendMessage(ChatColor.RED + "/scoreboard objectives add <name> <criteriaType> [display name ...]");
					return false;
				}
				String name = args[2];
				String criteria = args[3];

				if (criteria == null) {
					sender.sendMessage(ChatColor.RED + "Invalid objective criteria type. Valid types are: " + stringCollectionToString(OBJECTIVES_CRITERIA));
				} else if (name.length() > 16) {
					sender.sendMessage(ChatColor.RED + "The name '" + name + "' is too long for an objective, it can be at most 16 characters long");
				} else if (scoreboard.getObjective(name) != null) {
					sender.sendMessage(ChatColor.RED + "An objective with the name '" + name + "' already exists");
				} else {
					String displayName = null;
					if (args.length > 4) {
						displayName = StringUtils.join(ArrayUtils.subarray(args, 4, args.length), ' ');
						if (displayName.length() > 32) {
							sender.sendMessage(ChatColor.RED + "The name '" + displayName + "' is too long for an objective, it can be at most 32 characters long");
							return false;
						}
					}
					Objective objective = scoreboard.registerNewObjective(name, criteria);
					if (displayName != null && displayName.length() > 0) {
						objective.setDisplayName(displayName);
					}
					sender.sendMessage("Added new objective '" + name + "' successfully");
				}
			} else if (args[1].equalsIgnoreCase("remove")) {
				if (args.length != 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard objectives remove <name>");
					return false;
				}
				String name = args[2];
				Objective objective = scoreboard.getObjective(name);
				if (objective == null) {
					sender.sendMessage(ChatColor.RED + "No objective was found by the name '" + name + "'");
				} else {
					objective.unregister();
					sender.sendMessage("Removed objective '" + name + "' successfully");
				}
			} else if (args[1].equalsIgnoreCase("setdisplay")) {
				if (args.length != 3 && args.length != 4) {
					sender.sendMessage(ChatColor.RED + "/scoreboard objectives setdisplay <slot> [objective]");
					return false;
				}
				String slotName = args[2];
				DisplaySlot slot = OBJECTIVES_DISPLAYSLOT.get(slotName);
				if (slot == null) {
					sender.sendMessage(ChatColor.RED + "No such display slot '" + slotName + "'");
				} else {
					if (args.length == 4) {
						String objectiveName = args[3];
						Objective objective = scoreboard.getObjective(objectiveName);
						if (objective == null) {
							sender.sendMessage(ChatColor.RED + "No objective was found by the name '" + objectiveName + "'");
							return false;
						}

						objective.setDisplaySlot(slot);
						sender.sendMessage("Set the display objective in slot '" + slotName + "' to '" + objective.getName() + "'");
					} else {
						Objective objective = scoreboard.getObjective(slot);
						if (objective != null) {
							objective.setDisplaySlot(null);
						}
						sender.sendMessage("Cleared objective display slot '" + slotName + "'");
					}
				}
			}
		} else if (args[0].equalsIgnoreCase("players")) {
			if (args.length == 1) {
				sender.sendMessage(ChatColor.RED + "/scoreboard players <set|add|remove|reset|list>");
				return false;
			}
			if (args[1].equalsIgnoreCase("set") || args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove")) {
				if (args.length != 5) {
					if (args[1].equalsIgnoreCase("set")) {
						sender.sendMessage(ChatColor.RED + "/scoreboard players set <player> <objective> <score>");
					} else if (args[1].equalsIgnoreCase("add")) {
						sender.sendMessage(ChatColor.RED + "/scoreboard players add <player> <objective> <count>");
					} else {
						sender.sendMessage(ChatColor.RED + "/scoreboard players remove <player> <objective> <count>");
					}
					return false;
				}
				String objectiveName = args[3];
				Objective objective = scoreboard.getObjective(objectiveName);
				if (objective == null) {
					sender.sendMessage(ChatColor.RED + "No objective was found by the name '" + objectiveName + "'");
					return false;
				} else if (!objective.isModifiable()) {
					sender.sendMessage(ChatColor.RED + "The objective '" + objectiveName + "' is read-only and cannot be set");
					return false;
				}

				String valueString = args[4];
				int value;
				try {
					value = Integer.parseInt(valueString);
				} catch (NumberFormatException e) {
					sender.sendMessage(ChatColor.RED + "'" + valueString + "' is not a valid number");
					return false;
				}
				if (value < 1 && !args[1].equalsIgnoreCase("set")) {
					sender.sendMessage(ChatColor.RED + "The number you have entered (" + value + ") is too small, it must be at least 1");
					return false;
				}

				String playerName = args[2];
				if (playerName.length() > 16) {
					sender.sendMessage(ChatColor.RED + "'" + playerName + "' is too long for a player name");
					return false;
				}
				Score score = objective.getScore(Bukkit.getOfflinePlayer(playerName));
				int newScore;
				if (args[1].equalsIgnoreCase("set")) {
					newScore = value;
				} else if (args[1].equalsIgnoreCase("add")) {
					newScore = score.getScore() + value;
				} else {
					newScore = score.getScore() - value;
				}
				score.setScore(newScore);
				sender.sendMessage("Set score of " + objectiveName + " for player " + playerName + " to " + newScore);
			} else if (args[1].equalsIgnoreCase("reset")) {
				if (args.length != 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard players reset <player>");
					return false;
				}
				String playerName = args[2];
				if (playerName.length() > 16) {
					sender.sendMessage(ChatColor.RED + "'" + playerName + "' is too long for a player name");
					return false;
				}
				scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
				sender.sendMessage("Reset all scores of player " + playerName);
			} else if (args[1].equalsIgnoreCase("list")) {
				if (args.length > 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard players list <player>");
					return false;
				}
				if (args.length == 2) {
					Set<OfflinePlayer> players = scoreboard.getPlayers();
					if (players.isEmpty()) {
						sender.sendMessage(ChatColor.RED + "There are no tracked players on the scoreboard");
					} else {
						sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + players.size() + " tracked players on the scoreboard");
						sender.sendMessage(offlinePlayerSetToString(players));
					}
				} else {
					String playerName = args[2];
					if (playerName.length() > 16) {
						sender.sendMessage(ChatColor.RED + "'" + playerName + "' is too long for a player name");
						return false;
					}
					Set<Score> scores = scoreboard.getScores(Bukkit.getOfflinePlayer(playerName));
					if (scores.isEmpty()) {
						sender.sendMessage(ChatColor.RED + "Player " + playerName + " has no scores recorded");
					} else {
						sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + scores.size() + " tracked objective(s) for " + playerName);
						for (Score score : scores) {
							sender.sendMessage("- " + score.getObjective().getDisplayName() + ": " + score.getScore() + " (" + score.getObjective().getName() + ")");
						}
					}
				}
			}
		} else if (args[0].equalsIgnoreCase("teams")) {
			if (args.length == 1) {
				sender.sendMessage(ChatColor.RED + "/scoreboard teams <list|add|remove|empty|join|leave|option>");
				return false;
			}
			if (args[1].equalsIgnoreCase("list")) {
				if (args.length == 2) {
					Set<Team> teams = scoreboard.getTeams();
					if (teams.isEmpty()) {
						sender.sendMessage(ChatColor.RED + "There are no teams registered on the scoreboard");
					} else {
						sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + teams.size() + " teams on the scoreboard");
						for (Team team : teams) {
							sender.sendMessage("- " + team.getName() + ": '" + team.getDisplayName() + "' has " + team.getSize() + " players");
						}
					}
				} else if (args.length == 3) {
					String teamName = args[2];
					Team team = scoreboard.getTeam(teamName);
					if (team == null) {
						sender.sendMessage(ChatColor.RED + "No team was found by the name '" + teamName + "'");
					} else {
						Set<OfflinePlayer> players = team.getPlayers();
						if (players.isEmpty()) {
							sender.sendMessage(ChatColor.RED + "Team " + team.getName() + " has no players");
						} else {
							sender.sendMessage(ChatColor.DARK_GREEN + "Showing " + players.size() + " player(s) in team " + team.getName());
							sender.sendMessage(offlinePlayerSetToString(players));
						}
					}
				} else {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams list [name]");
					return false;
				}
			} else if (args[1].equalsIgnoreCase("add")) {
				if (args.length < 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams add <name> [display name ...]");
					return false;
				}
				String name = args[2];
				if (name.length() > 16) {
					sender.sendMessage(ChatColor.RED + "The name '" + name + "' is too long for a team, it can be at most 16 characters long");
				} else if (scoreboard.getTeam(name) != null) {
					sender.sendMessage(ChatColor.RED + "A team with the name '" + name + "' already exists");
				} else {
					String displayName = null;
					if (args.length > 3) {
						displayName = StringUtils.join(ArrayUtils.subarray(args, 3, args.length), ' ');
						if (displayName.length() > 32) {
							sender.sendMessage(ChatColor.RED + "The display name '" + displayName + "' is too long for a team, it can be at most 32 characters long");
							return false;
						}
					}
					Team team = scoreboard.registerNewTeam(name);
					if (displayName != null && displayName.length() > 0) {
						team.setDisplayName(displayName);
					}
					sender.sendMessage("Added new team '" + team.getName() + "' successfully");
				}
			} else if (args[1].equalsIgnoreCase("remove")) {
				if (args.length != 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams remove <name>");
					return false;
				}
				String name = args[2];
				Team team = scoreboard.getTeam(name);
				if (team == null) {
					sender.sendMessage(ChatColor.RED + "No team was found by the name '" + name + "'");
				} else {
					team.unregister();
					sender.sendMessage("Removed team " + name);
				}
			} else if (args[1].equalsIgnoreCase("empty")) {
				if (args.length != 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams clear <name>");
					return false;
				}
				String name = args[2];
				Team team = scoreboard.getTeam(name);
				if (team == null) {
					sender.sendMessage(ChatColor.RED + "No team was found by the name '" + name + "'");
				} else {
					Set<OfflinePlayer> players = team.getPlayers();
					if (players.isEmpty()) {
						sender.sendMessage(ChatColor.RED + "Team " + team.getName() + " is already empty, cannot remove nonexistant players");
					} else {
						for (OfflinePlayer player : players) {
							team.removePlayer(player);
						}
						sender.sendMessage("Removed all " + players.size() + " player(s) from team " + team.getName());
					}
				}
			} else if (args[1].equalsIgnoreCase("join")) {
				if ((sender instanceof Player) ? args.length < 3 : args.length < 4) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams join <team> [player...]");
					return false;
				}
				String teamName = args[2];
				Team team = scoreboard.getTeam(teamName);
				if (team == null) {
					sender.sendMessage(ChatColor.RED + "No team was found by the name '" + teamName + "'");
				} else {
					Set<String> addedPlayers = new HashSet<String>();
					if ((sender instanceof Player) && args.length == 3) {
						team.addPlayer((Player) sender);
						addedPlayers.add(sender.getName());
					} else {
						for (int i = 3; i < args.length; i++) {
							String playerName = args[i];
							OfflinePlayer offlinePlayer;
							Player player = Bukkit.getPlayerExact(playerName);
							if (player != null) {
								offlinePlayer = player;
							} else {
								offlinePlayer = Bukkit.getOfflinePlayer(playerName);
							}
							team.addPlayer(offlinePlayer);
							addedPlayers.add(offlinePlayer.getName());

							// AUTOREFEREE START - check for AutoRefTeam
							if (match != null)
							{
								AutoRefTeam autorefteam = match.getScoreboardTeam(teamName);
								if (autorefteam != null && player != null)
								{
									PlayerTeamJoinEvent.Reason reason = PlayerTeamJoinEvent.Reason.AUTOMATIC;
									match.joinTeam(player, autorefteam, reason, false);
								}
							}
							// AUTOREFEREE END
						}
					}
					sender.sendMessage("Added " + addedPlayers.size() + " player(s) to team " + team.getName() + ": " + stringCollectionToString(addedPlayers));
				}
			} else if (args[1].equalsIgnoreCase("leave")) {
				if (!(sender instanceof Player) && args.length < 3) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams leave [player...]");
					return false;
				}
				Set<String> left = new HashSet<String>();
				Set<String> noTeam = new HashSet<String>();
				if ((sender instanceof Player) && args.length == 2) {
					Team team = scoreboard.getPlayerTeam((Player) sender);
					if (team != null) {
						team.removePlayer((Player) sender);
						left.add(sender.getName());
					} else {
						noTeam.add(sender.getName());
					}
				} else {
					for (int i = 2; i < args.length; i++) {
						String playerName = args[i];
						OfflinePlayer offlinePlayer;
						Player player = Bukkit.getPlayerExact(playerName);
						if (player != null) {
							offlinePlayer = player;
						} else {
							offlinePlayer = Bukkit.getOfflinePlayer(playerName);
						}
						Team team = scoreboard.getPlayerTeam(offlinePlayer);
						if (team != null) {
							team.removePlayer(offlinePlayer);
							left.add(offlinePlayer.getName());
						} else {
							noTeam.add(offlinePlayer.getName());
						}
					}
				}
				if (!left.isEmpty()) {
					sender.sendMessage("Removed " + left.size() + " player(s) from their teams: " + stringCollectionToString(left));
				}
				if (!noTeam.isEmpty()) {
					sender.sendMessage("Could not remove " + noTeam.size() + " player(s) from their teams: " + stringCollectionToString(noTeam));
				}
			} else if (args[1].equalsIgnoreCase("option")) {
				if (args.length != 4 && args.length != 5) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams option <team> <friendlyfire|color|seefriendlyinvisibles> <value>");
					return false;
				}
				String teamName = args[2];
				Team team = scoreboard.getTeam(teamName);
				if (team == null) {
					sender.sendMessage(ChatColor.RED + "No team was found by the name '" + teamName + "'");
					return false;
				}
				String option = args[3].toLowerCase();
				if (!option.equals("friendlyfire") && !option.equals("color") && !option.equals("seefriendlyinvisibles")) {
					sender.sendMessage(ChatColor.RED + "/scoreboard teams option <team> <friendlyfire|color|seefriendlyinvisibles> <value>");
					return false;
				}
				if (args.length == 4) {
					if (option.equals("color")) {
						sender.sendMessage(ChatColor.RED + "Valid values for option color are: " + stringCollectionToString(TEAMS_OPTION_COLOR.keySet()));
					} else {
						sender.sendMessage(ChatColor.RED + "Valid values for option " + option + " are: true and false");
					}
				} else {
					String value = args[4].toLowerCase();
					if (option.equals("color")) {
						ChatColor color = TEAMS_OPTION_COLOR.get(value);
						if (color == null) {
							sender.sendMessage(ChatColor.RED + "Valid values for option color are: " + stringCollectionToString(TEAMS_OPTION_COLOR.keySet()));
							return false;
						}
						team.setPrefix(color.toString());
						team.setSuffix(ChatColor.RESET.toString());
					} else {
						if (!value.equals("true") && !value.equals("false")) {
							sender.sendMessage(ChatColor.RED + "Valid values for option " + option + " are: true and false");
							return false;
						}
						if (option.equals("friendlyfire")) {
							team.setAllowFriendlyFire(value.equals("true"));
						} else {
							team.setCanSeeFriendlyInvisibles(value.equals("true"));
						}
					}
					sender.sendMessage("Set option " + option + " for team " + team.getName() + " to " + value);
				}
			}
		} else {
			sender.sendMessage(ChatColor.RED + "Usage: /scoreboard <objectives|players|teams>");
			return false;
		}
		return true;
	}

	// github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java
	private static String offlinePlayerSetToString(Set<OfflinePlayer> set) {
		StringBuilder string = new StringBuilder();
		String lastValue = null;
		for (OfflinePlayer value : set) {
			string.append(lastValue = value.getName()).append(", ");
		}
		string.delete(string.length() - 2, Integer.MAX_VALUE);
		if (string.length() != lastValue.length()) {
			string.insert(string.length() - lastValue.length(), "and ");
		}
		return string.toString();
	}

	// github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/command/defaults/ScoreboardCommand.java
	private static String stringCollectionToString(Collection<String> set) {
		StringBuilder string = new StringBuilder();
		String lastValue = null;
		for (String value : set) {
			string.append(lastValue = value).append(", ");
		}
		string.delete(string.length() - 2, Integer.MAX_VALUE);
		if (string.length() != lastValue.length()) {
			string.insert(string.length() - lastValue.length(), "and ");
		}
		return string.toString();
	}
}
